package de.tudarmstadt.gdi1.project.analyse;

import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import de.tudarmstadt.gdi1.project.Charset;
import de.tudarmstadt.gdi1.project.alphabet.Alphabet;
import de.tudarmstadt.gdi1.project.alphabet.Dictionary;
import de.tudarmstadt.gdi1.project.alphabet.Distribution;
import de.tudarmstadt.gdi1.project.analysis.ValidateDecryptionOracle;
import de.tudarmstadt.gdi1.project.analysis.monoalphabetic.BacktrackingAnalysis;
import de.tudarmstadt.gdi1.project.analysis.monoalphabetic.MonoalphabeticCribCryptanalysis;
import de.tudarmstadt.gdi1.project.substitution.mono.Monoalphabetic;

public class BacktrackingAnalyse implements MonoalphabeticCribCryptanalysis,
		BacktrackingAnalysis {

	Map<Character, Character> m = new TreeMap<Character, Character>();
	Alphabet keyAlphabet;
	Monoalphabetic encode;
	boolean foundKey = false;
	int iterations = 0;
	StringBuilder path = new StringBuilder();

	@Override
	public Map<Character, Character> reconstructKey(
			Map<Character, Character> key, String ciphertext,
			Alphabet alphabet, Distribution distribution,
			Dictionary dictionary, List<String> cribs,
			ValidateDecryptionOracle validateDecryptionOracle) {
		if (alphabet.size() > 12) {
			return null;
		} else {
			if ((key.size() == alphabet.size())) {
				iterations++;
				keyAlphabet = new Charset(key.values());
				encode.setCipherText(keyAlphabet);
				foundKey = validateDecryptionOracle.isCorrect(encode
						.decrypt(ciphertext));
			} else {
				char current = getNextSourceChar(key, alphabet, distribution,
						dictionary, cribs);
				for (char a : getPotentialAssignments(current, key, ciphertext,
						alphabet, distribution, dictionary)) {
					if (!foundKey) {
						key.put(current, a);
						path.append(current);
						reconstructKey(key, ciphertext, alphabet, distribution,
								dictionary, cribs, validateDecryptionOracle);
						if (!foundKey) {
							key.remove(current);
							path.deleteCharAt(path.length() - 1);
						}
					} else
						break;
				}
			}
			return key;
		}
	}

	@Override
	public Collection<Character> getPotentialAssignments(
			Character targetCharacter, Map<Character, Character> key,
			String ciphertext, Alphabet alphabet, Distribution distribution,
			Dictionary dictionary) {
		LinkedList<Character> l = new LinkedList<Character>();
		for (int i = 0; i < alphabet.size(); i++) {
			if (!key.values().contains(
					alphabet.getChar(alphabet.getIndex(targetCharacter) + i)))
				l.add(alphabet.getChar(alphabet.getIndex(targetCharacter) + i));
		}
		return l;
	}

	@Override
	public Character getNextSourceChar(Map<Character, Character> key,
			Alphabet alphabet, Distribution distribution,
			Dictionary dictionary, List<String> cribs) {
		char newchar = 0;
		for (char c : alphabet) {
			if (!key.containsKey(c)) {
				newchar = c;
				break;
			}
		}
		return newchar;
	}

	@Override
	public boolean isPromisingPath(Alphabet alphabet, String ciphertext,
			Map<Character, Character> key, Distribution distribution,
			Dictionary dictionary, Collection<String> cribs) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public char[] knownCiphertextAttack(String ciphertext,
			Distribution distribution, Dictionary dictionary, List<String> cribs) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public char[] knownCiphertextAttack(String ciphertext,
			Distribution distribution, Dictionary dictionary,
			List<String> cribs,
			ValidateDecryptionOracle validateDecryptionOracle) {
		Alphabet alphabet = distribution.getAlphabet();
		encode = new Monoalphabetic(alphabet);
		char[] c = new char[alphabet.size()];
		int i = 0;
		Map<Character, Character> x = reconstructKey(m, ciphertext, alphabet,
				distribution, dictionary, cribs, validateDecryptionOracle);
		for (char a : x.values()) {
			c[i++] = a;
		}
		return c;
	}

	public int[] matches(Alphabet key, Alphabet reconKey) {
		int[] a = new int[key.size()];
		for (int i = 0; i < reconKey.size(); i++) {
			if (reconKey.getChar(i) == key.getChar(i)) {
				a[i] = 1;
			} else
				a[i] = 0;
		}
		return a;
	}

	public int corrects(Alphabet source, Alphabet key, Alphabet reconKey,
			String path) {
		int v = 0;
		int[] a = matches(key, reconKey);
		boolean check = true;
		for (int i = 0; i < path.length(); i++) {
			if ((a[source.getIndex(path.charAt(i))] == 1) && check) {
				v++;
			} else
				check = false;
		}
		return v;
	}

	@Override
	public String getState(Alphabet sourceAlphabet, Alphabet targetKey) {
		if (keyAlphabet == null) {
			keyAlphabet = sourceAlphabet;
		}
		String pathstring = path.toString();
		StringBuilder info = new StringBuilder();
		info.append(Arrays.toString(sourceAlphabet.asCharArray()))
				.append("\n")
				.append(Arrays.toString(targetKey.asCharArray()))
				.append("\n")
				.append(Arrays.toString(keyAlphabet.asCharArray()))
				.append("\n")
				.append(Arrays.toString(matches(targetKey, keyAlphabet)))
				.append("\n")
				.append("iterations: ")
				.append(iterations)
				.append("\n")
				.append("path: ")
				.append(pathstring)
				.append("\n")
				.append("correct: ")
				.append(corrects(sourceAlphabet, targetKey, keyAlphabet,
						pathstring));
		return info.toString();
	}

}
